`include "common.svh"


typedef enum logic [4:0] {
  OP_LOAD     = 5'b00_000,
  OP_IMM      = 5'b00_100,
  OP_AUIPC    = 5'b00_101,
  OP_IMM32    = 5'b00_110,
  OP_MISC_MEM = 5'b00_011,
  OP_STORE    = 5'b01_000,
  OP_OP       = 5'b01_100,
  OP_LUI      = 5'b01_101,
  OP_32       = 5'b01_110,
  OP_BRANCH   = 5'b11_000,
  OP_JALR     = 5'b11_001,
  OP_JAL      = 5'b11_011,
  OP_SYSTEM   = 5'b11_100
} OP_CODE;

module rv_pre_decoder (
    input word_t pc,
    input [31:0] inst,

    output BranchType branch_type,
    output target_valid,
    output word_t target
);

  wire [6:0] opcode = inst[6:0];
  wire is_32b = opcode[1:0] == 2'b11;

  wire [4:0] rd = inst[11:7];
  wire rd_link = rd == 5'd1;

  wire [31:0] opcode_onehot  /*verilator public_flat*/;
  decoder #(5) opcode_decoder (
      .din (opcode[6:2]),
      .dout(opcode_onehot)
  );

  wire rv_jal = is_32b & opcode_onehot[OP_JAL];
  assign branch_type = rv_jal ? (rd_link ? BT_Call : BT_Jump) : BT_None;
  wire [31:0] immJ = {{11{inst[31]}}, inst[31], inst[19:12], inst[20], inst[30:21], 1'b0};
  assign target = pc + `IMM_WORD(immJ);
  assign target_valid = rv_jal;
endmodule


module rv_decoder (
    input [31:0] inst,
    output DecodeOP dop
);
  wire [4:0] rs1 = inst[19:15];
  wire [4:0] rs2 = inst[24:20];
  wire [4:0] rd = inst[11:7];

  wire [31:0] immI = {{20{inst[31]}}, inst[31:20]};
  wire [31:0] immU = {inst[31:12], 12'b0};
  wire [31:0] immS = {{20{inst[31]}}, inst[31:25], inst[11:7]};
  wire [31:0] immJ = {{11{inst[31]}}, inst[31], inst[19:12], inst[20], inst[30:21], 1'b0};
  wire [31:0] immB = {{19{inst[31]}}, inst[31], inst[7], inst[30:25], inst[11:8], 1'b0};

  wire [6:0] opcode = inst[6:0];
  wire [2:0] funct3 = inst[14:12];
  wire [6:0] funct7 = inst[31:25];

  wire is_32b = opcode[1:0] == 2'b11;

  wire [31:0] opcode_onehot  /*verilator public_flat*/;
  decoder #(5) opcode_decoder (
      .din (opcode[6:2]),
      .dout(opcode_onehot)
  );
  wire [7:0] funct3_onehot  /*verilator public_flat*/;
  decoder #(3) funct3_decoder (
      .din (funct3),
      .dout(funct3_onehot)
  );

  //RV64I
  wire rv_auipc = is_32b & opcode_onehot[OP_AUIPC];
  wire rv_lui = is_32b & opcode_onehot[OP_LUI];
  wire rv_addiw = is_32b & opcode_onehot[OP_IMM32] & funct3_onehot[3'b000];
  wire rv_addw = is_32b & opcode_onehot[OP_32] & funct3_onehot[3'b000] & funct7 == 7'b0000000;
  wire rv_ld = is_32b & opcode_onehot[OP_LOAD] & funct3_onehot[3'b011];
  wire rv_lwu = is_32b & opcode_onehot[OP_LOAD] & funct3_onehot[3'b110];
  wire rv_sd = is_32b & opcode_onehot[OP_STORE] & funct3_onehot[3'b011];
  wire rv_slli = is_32b & opcode_onehot[OP_IMM] & funct3_onehot[3'b001] & funct7[6:1] == 6'b000000;
  wire rv_slliw = is_32b & opcode_onehot[OP_IMM32] & funct3_onehot[3'b001] & funct7 == 7'b0000000;
  wire rv_sllw = is_32b & opcode_onehot[OP_32] & funct3_onehot[3'b001] & funct7 == 7'b0000000;
  wire rv_srai = is_32b & opcode_onehot[OP_IMM] & funct3_onehot[3'b101] & funct7[6:1] == 6'b010000;
  wire rv_sraiw = is_32b & opcode_onehot[OP_IMM32] & funct3_onehot[3'b101] & funct7 == 7'b0100000;
  wire rv_sraw = is_32b & opcode_onehot[OP_32] & funct3_onehot[3'b101] & funct7 == 7'b0100000;
  wire rv_srli = is_32b & opcode_onehot[OP_IMM] & funct3_onehot[3'b101] & funct7[6:1] == 6'b000000;
  wire rv_srliw = is_32b & opcode_onehot[OP_IMM32] & funct3_onehot[3'b101] & funct7 == 7'b0000000;
  wire rv_srlw = is_32b & opcode_onehot[OP_32] & funct3_onehot[3'b101] & funct7 == 7'b0000000;
  wire rv_subw = is_32b & opcode_onehot[OP_32] & funct3_onehot[3'b000] & funct7 == 7'b0100000;
  //rv64m
  wire rv_divuw = is_32b & opcode_onehot[OP_32] & funct3_onehot[3'b101] & funct7 == 7'b0000001;
  wire rv_divw = is_32b & opcode_onehot[OP_32] & funct3_onehot[3'b100] & funct7 == 7'b0000001;
  wire rv_remuw = is_32b & opcode_onehot[OP_32] & funct3_onehot[3'b111] & funct7 == 7'b0000001;
  wire rv_remw = is_32b & opcode_onehot[OP_32] & funct3_onehot[3'b110] & funct7 == 7'b0000001;
  wire rv_mulw = is_32b & opcode_onehot[OP_32] & funct3_onehot[3'b000] & funct7 == 7'b0000001;
  //rv32m
  wire rv_div = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b100] & funct7 == 7'b0000001;
  wire rv_divu = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b101] & funct7 == 7'b0000001;
  wire rv_remu = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b111] & funct7 == 7'b0000001;
  wire rv_rem = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b110] & funct7 == 7'b0000001;
  wire rv_mul = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b000] & funct7 == 7'b0000001;
  wire rv_mulh = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b001] & funct7 == 7'b0000001;
  wire rv_mulhsu = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b010] & funct7 == 7'b0000001;
  wire rv_mulhu = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b011] & funct7 == 7'b0000001;
  //RV32I
  wire rv_add = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b000] & funct7 == 7'b0000000;
  wire rv_addi = is_32b & opcode_onehot[OP_IMM] & funct3_onehot[3'b000];
  wire rv_and = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b111] & funct7 == 7'b0000000;
  wire rv_andi = is_32b & opcode_onehot[OP_IMM] & funct3_onehot[3'b111];
  wire rv_beq = is_32b & opcode_onehot[OP_BRANCH] & funct3_onehot[3'b000];
  wire rv_bge = is_32b & opcode_onehot[OP_BRANCH] & funct3_onehot[3'b101];
  wire rv_bgeu = is_32b & opcode_onehot[OP_BRANCH] & funct3_onehot[3'b111];
  wire rv_blt = is_32b & opcode_onehot[OP_BRANCH] & funct3_onehot[3'b100];
  wire rv_bltu = is_32b & opcode_onehot[OP_BRANCH] & funct3_onehot[3'b110];
  wire rv_bne = is_32b & opcode_onehot[OP_BRANCH] & funct3_onehot[3'b001];
  wire rv_jal = is_32b & opcode_onehot[OP_JAL];
  wire rv_jalr = is_32b & opcode_onehot[OP_JALR] & funct3_onehot[3'b000];
  wire rv_lb = is_32b & opcode_onehot[OP_LOAD] & funct3_onehot[3'b000];
  wire rv_lbu = is_32b & opcode_onehot[OP_LOAD] & funct3_onehot[3'b100];
  wire rv_lh = is_32b & opcode_onehot[OP_LOAD] & funct3_onehot[3'b001];
  wire rv_lhu = is_32b & opcode_onehot[OP_LOAD] & funct3_onehot[3'b101];
  wire rv_lw = is_32b & opcode_onehot[OP_LOAD] & funct3_onehot[3'b010];
  wire rv_or = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b110] & funct7 == 7'b0000000;
  wire rv_ori = is_32b & opcode_onehot[OP_IMM] & funct3_onehot[3'b110];
  wire rv_sb = is_32b & opcode_onehot[OP_STORE] & funct3_onehot[3'b000];
  wire rv_sh = is_32b & opcode_onehot[OP_STORE] & funct3_onehot[3'b001];
  wire rv_sw = is_32b & opcode_onehot[OP_STORE] & funct3_onehot[3'b010];
  wire rv_sll = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b001] & funct7 == 7'b0000000;
  wire rv_slt = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b010] & funct7 == 7'b0000000;
  wire rv_slti = is_32b & opcode_onehot[OP_IMM] & funct3_onehot[3'b010];
  wire rv_sltiu = is_32b & opcode_onehot[OP_IMM] & funct3_onehot[3'b011];
  wire rv_sltu = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b011] & funct7 == 7'b0000000;
  wire rv_sra = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b101] & funct7 == 7'b0100000;
  wire rv_srl = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b101] & funct7 == 7'b0000000;
  wire rv_sub = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b000] & funct7 == 7'b0100000;
  wire rv_xor = is_32b & opcode_onehot[OP_OP] & funct3_onehot[3'b100] & funct7 == 7'b0000000;
  wire rv_xori = is_32b & opcode_onehot[OP_IMM] & funct3_onehot[3'b100];
  wire rv_ebreak = inst == 32'b0000000_00001_00000_000_00000_11100_11;
  wire rv_ecall = inst == 32'b0000000_00000_00000_000_00000_11100_11;
  //   wire rv_fence  = is_32b & opcode_onehot[OP_MISC_MEM] & funct3_onehot[3'b000];

  //ZiCSR
  wire rv_csrrw = is_32b & opcode_onehot[OP_SYSTEM] & funct3_onehot[3'b001];
  wire rv_csrrs = is_32b & opcode_onehot[OP_SYSTEM] & funct3_onehot[3'b010];
  wire rv_csrrc = is_32b & opcode_onehot[OP_SYSTEM] & funct3_onehot[3'b011];
  wire rv_csrrwi = is_32b & opcode_onehot[OP_SYSTEM] & funct3_onehot[3'b101];
  wire rv_csrrsi = is_32b & opcode_onehot[OP_SYSTEM] & funct3_onehot[3'b110];
  wire rv_csrrci = is_32b & opcode_onehot[OP_SYSTEM] & funct3_onehot[3'b111];
  wire rv_mret = inst == 32'b0011000_00010_00000_000_00000_11100_11;

  wire rv_csr_op = rv_csrrw | rv_csrrs | rv_csrrc | rv_csrrwi | rv_csrrsi | rv_csrrci;

  wire type_I = rv_addiw|rv_slli|rv_slliw|rv_srai|rv_sraiw|rv_srli|rv_srliw|rv_addi|rv_andi|rv_ori|rv_slti|rv_sltiu|rv_xori|rv_ld|rv_lwu|rv_jalr|rv_lb|rv_lbu|rv_lh|rv_lhu|rv_lw|rv_csrrw|rv_csrrs|rv_csrrc|rv_csrrwi|rv_csrrsi|rv_csrrci;
  wire type_U = rv_auipc | rv_lui;
  wire type_S = rv_sd | rv_sb | rv_sh | rv_sw;
  wire type_J = rv_jal;
  wire type_B = rv_beq | rv_bge | rv_bgeu | rv_blt | rv_bltu | rv_bne;
  wire type_R = rv_addw|rv_sllw|rv_sraw|rv_srlw|rv_subw|rv_add|rv_and|rv_or|rv_sll|rv_slt|rv_sltu|rv_sra|rv_srl|rv_sub|rv_xor|rv_divuw|rv_divw|rv_remuw|rv_remw|rv_div|rv_divu|rv_remu|rv_rem|rv_mulw|rv_mul|rv_mulh|rv_mulhsu|rv_mulhu;


  wire rd_valid = type_R | type_I | type_U | type_J;
  wire rs1_valid = type_R | type_I | type_S | type_B;
  wire rs2_valid = type_R | type_S | type_B;

  assign dop.has_rd = rd_valid && (rd != 5'b0);
  assign dop.is_op32 = is_32b & (opcode_onehot[OP_IMM32] | opcode_onehot[OP_32]);
  assign dop.has_imm  = is_32b & (opcode_onehot[OP_IMM32] | opcode_onehot[OP_IMM] | opcode_onehot[OP_BRANCH] | opcode_onehot[OP_LOAD] |opcode_onehot[OP_STORE] | opcode_onehot[OP_AUIPC] | opcode_onehot[OP_LUI] |opcode_onehot[OP_JAL] | opcode_onehot[OP_JALR] | rv_csr_op);
  assign dop.flush_pipeline = is_32b & opcode_onehot[OP_SYSTEM] | opcode_onehot[OP_MISC_MEM];
  logic [31:0] imm32;

  localparam SRC_Type_Bits = $size(SRC_Type);
  wire src1_zero = 'b0;
  wire src1_reg = type_I | type_R | type_S | type_B;
  wire src1_pc = rv_auipc;
  wire src1_imm = 'b0;

  MUX_OH #(4, SRC_Type_Bits) src1_type_mux (
      .sel ({src1_zero, src1_reg, src1_pc, src1_imm}),
      .din ({SRC_ZERO, SRC_REG, SRC_PC, SRC_IMM}),
      .dout(dop.src1_type)
  );

  wire src2_zero = 'b0;
  wire src2_reg = type_R | type_S | type_B;
  wire src2_pc = 'b0;
  wire src2_imm = type_I | type_J | type_U;

  MUX_OH #(4, SRC_Type_Bits) src2_type_mux (
      .sel ({src2_zero, src2_reg, src2_pc, src2_imm}),
      .din ({SRC_ZERO, SRC_REG, SRC_PC, SRC_IMM}),
      .dout(dop.src2_type)
  );


  MUX_OH #(5, 32) imm32_mux (
      .sel ({type_I, type_U, type_S, type_J, type_B}),
      .din ({immI, immU, immS, immJ, immB}),
      .dout(imm32)
  );
  //TO ALU
  wire alu_add = |{rv_addw, rv_addiw, rv_add, rv_addi, rv_auipc, rv_lui};
  wire alu_sub = |{rv_subw, rv_sub};
  wire alu_and = |{rv_and, rv_andi};
  wire alu_or = |{rv_or, rv_ori};
  wire alu_xor = |{rv_xor, rv_xori};
  wire alu_sll = |{rv_sll, rv_slli, rv_slliw, rv_sllw};
  wire alu_sra = |{rv_sra, rv_srai, rv_sraiw, rv_sraw};
  wire alu_srl = |{rv_srl, rv_srli, rv_srliw, rv_srlw};
  wire alu_slt = |{rv_slt, rv_slti};
  wire alu_sltu = |{rv_sltiu, rv_sltu};

  wire [9:0] alu_sel = {
    alu_add, alu_sub, alu_and, alu_or, alu_xor, alu_sll, alu_sra, alu_srl, alu_slt, alu_sltu
  };
  ALU_OP alu_op;
  MUX_OH #(10, $size(
      FU_OP
  )) alu_op_mux (
      .sel(alu_sel),
      .din({
        ALU_ADD, ALU_SUB, ALU_AND, ALU_OR, ALU_XOR, ALU_SLL, ALU_SRA, ALU_SRL, ALU_SLT, ALU_SLTU
      }),
      .dout(alu_op)
  );
  //TO BRU
  wire bru_beq = rv_beq;
  wire bru_bge = rv_bge;
  wire bru_bgeu = rv_bgeu;
  wire bru_blt = rv_blt;
  wire bru_bltu = rv_bltu;
  wire bru_bne = rv_bne;
  wire bru_jal = rv_jal;
  wire bru_jalr = rv_jalr;
  wire [7:0] bru_sel = {bru_beq, bru_bge, bru_bgeu, bru_blt, bru_bltu, bru_bne, bru_jal, bru_jalr};

  BRU_OP bru_op;
  MUX_OH #(8, $size(
      FU_OP
  )) bru_op_mux (
      .sel (bru_sel),
      .din ({BRU_BEQ, BRU_BGE, BRU_BGEU, BRU_BLT, BRU_BLTU, BRU_BNE, BRU_JAL, BRU_JALR}),
      .dout(bru_op)
  );

  //TO LSU
  wire lsu_lb = rv_lb;
  wire lsu_lbu = rv_lbu;
  wire lsu_lh = rv_lh;
  wire lsu_lhu = rv_lhu;
  wire lsu_lw = rv_lw;
  wire lsu_sb = rv_sb;
  wire lsu_sh = rv_sh;
  wire lsu_sw = rv_sw;
  wire lsu_ld = rv_ld;
  wire lsu_lwu = rv_lwu;
  wire lsu_sd = rv_sd;

  wire [10:0] lsu_sel = {
    lsu_lb, lsu_lbu, lsu_lh, lsu_lhu, lsu_lw, lsu_sb, lsu_sh, lsu_sw, lsu_ld, lsu_lwu, lsu_sd
  };

  LSU_OP lsu_op;
  MUX_OH #(11, $size(
      FU_OP
  )) lsu_op_mux (
      .sel(lsu_sel),
      .din({
        LSU_LB, LSU_LBU, LSU_LH, LSU_LHU, LSU_LW, LSU_SB, LSU_SH, LSU_SW, LSU_LD, LSU_LWU, LSU_SD
      }),
      .dout(lsu_op)
  );
  //TO MDU
  wire mdu_div = rv_div | rv_divw;
  wire mdu_divu = rv_divu | rv_divuw;
  wire mdu_remu = rv_remu | rv_remuw;
  wire mdu_rem = rv_rem | rv_remw;
  wire mdu_mul = rv_mul | rv_mulw;
  wire mdu_mulh = rv_mulh;
  wire mdu_mulhsu = rv_mulhsu;
  wire mdu_mulhu = rv_mulhu;

  wire [7:0] mdu_sel = {
    mdu_div, mdu_divu, mdu_remu, mdu_rem, mdu_mul, mdu_mulh, mdu_mulhsu, mdu_mulhu
  };

  MDU_OP mdu_op;
  MUX_OH #(8, $size(
      FU_OP
  )) mdu_op_mux (
      .sel (mdu_sel),
      .din ({MDU_DIV, MDU_DIVU, MDU_REMU, MDU_REM, MDU_MUL, MDU_MULH, MDU_MULHSU, MDU_MULHU}),
      .dout(mdu_op)
  );
  //TO MISC
  wire misc_csrrw = rv_csrrw;
  wire misc_csrrs = rv_csrrs;
  wire misc_csrrc = rv_csrrc;
  wire misc_csrrwi = rv_csrrwi;
  wire misc_csrrsi = rv_csrrsi;
  wire misc_csrrci = rv_csrrci;
  wire misc_mret = rv_mret;
  wire misc_ebreak = rv_ebreak;
  wire misc_ecall = rv_ecall;

  wire [8:0] misc_sel = {
    misc_csrrw,
    misc_csrrs,
    misc_csrrc,
    misc_csrrwi,
    misc_csrrsi,
    misc_csrrci,
    misc_mret,
    misc_ebreak,
    misc_ecall
  };

  MISC_OP misc_op;
  MUX_OH #(9, $size(
      FU_OP
  )) misc_op_mux (
      .sel(misc_sel),
      .din({
        MISC_CSRRW,
        MISC_CSRRS,
        MISC_CSRRC,
        MISC_CSRRWI,
        MISC_CSRRSI,
        MISC_CSRRCI,
        MISC_MRET,
        MISC_EBREAK,
        MISC_ECALL
      }),
      .dout(misc_op)
  );

  wire [4:0] fu_sel = {|alu_sel, |bru_sel, |lsu_sel, |mdu_sel, |misc_sel};
  wire unsupport_op = ~(|fu_sel);
  FU_Type fu_type;
  MUX_OH #(6, $size(
      FU_Type
  )) fu_type_mux (
      .sel ({fu_sel, unsupport_op}),
      .din ({FU_ALU, FU_BRU, FU_LSU, FU_MDU, FU_MISC, FU_MISC}),
      .dout(fu_type)
  );


  FU_OP fu_op;
  MUX_OH #(6, $size(
      FU_OP
  )) fu_op_mux (
      .sel ({fu_sel, unsupport_op}),
      .din ({alu_op, bru_op, lsu_op, mdu_op, misc_op, MISC_UNSUPPORT_OP}),
      .dout(fu_op)
  );

  wire rs1_link = dop.rs1 == 5'd1 || dop.rs1 == 5'd5;
  wire rd_link = dop.rd == 5'd1 || dop.rd == 5'd5;
  wire rs1_eq_rd = dop.rs1 == dop.rd;
  wire is_jmp = rv_jal | rv_jalr;
  wire is_branch = rv_beq | rv_bge | rv_bgeu | rv_blt | rv_bltu | rv_bne;

  BranchType jmp_type;
  always_comb begin
    case ({
      rd_link, rs1_link
    })
      2'b00:   jmp_type = BT_Jump;
      2'b01:   jmp_type = BT_Ret;
      2'b10:   jmp_type = BT_Call;
      2'b11:   jmp_type = rs1_eq_rd ? BT_Call : BT_CallRet;
      default: jmp_type = BT_Jump;
    endcase
  end
  assign dop.branch_type = is_jmp ? jmp_type : is_branch ? BT_Branch : BT_None;
  assign dop.illegal_inst = (~is_32b) | (~(|fu_sel));
  assign dop.rs1 = {5{rs1_valid}} & rs1;
  assign dop.rs2 = {5{rs2_valid}} & rs2;
  assign dop.rd = {5{rd_valid}} & rd;
  assign dop.imm32 = imm32;
  assign dop.fu_type = fu_type;
  assign dop.fu_op = fu_op;

endmodule


module decode #(
    parameter DECODE_WIDTH = 2
) (
    input clk,
    input rst,
    input flush,
    input i_valid,
    output i_ready,
    input i_ibuf_entry_valid[DECODE_WIDTH-1:0],
    input IBUF_Entry i_ibuf_entry[DECODE_WIDTH-1:0],

    output o_redirect_valid,
    output word_t o_redirect_pc,
    output o_redirect_bpu_res_valid,
    output BPU_Res o_redirect_bpu_res,

    output o_valid,
    input o_ready,
    output FrontendOP o_op[DECODE_WIDTH-1:0],
    output o_op_valid[DECODE_WIDTH-1:0]
);
  localparam REG_WIDTH = ($size(FrontendOP) + 1);
  logic [DECODE_WIDTH-1:0][REG_WIDTH-1:0] i_data;
  logic [DECODE_WIDTH-1:0][REG_WIDTH-1:0] o_data;
  pipe_stage #(REG_WIDTH * DECODE_WIDTH) pipeline_reg (
      .rst(rst | flush),
      .*
  );
  logic [DECODE_WIDTH-1:0] redirect_valid  /*verilator split_var*/;
  word_t redirect_pc[DECODE_WIDTH-1:0];
  BPU_Res redirect_bpu_res[DECODE_WIDTH-1:0];

  logic sel_redirect_valid;
  word_t sel_redirect_pc;
  BPU_Res sel_redirect_bpu_res;

  logic redirect_valid_r;
  wire i_fire = i_valid & i_ready;
  wire o_fire = o_valid & o_ready;
  assign o_redirect_valid = redirect_valid_r & o_fire;
  reg_lr #(1) o_redirect_valid_r (
      .rst (rst | flush),
      .load(i_fire),
      .din (sel_redirect_valid),
      .dout(redirect_valid_r),
      .*
  );

  reg_l #($size(
      BPU_Res
  ) + `WORD_BITS) o_redirect_data (
      .load(i_fire),
      .din ({sel_redirect_pc, sel_redirect_bpu_res}),
      .dout({o_redirect_pc, o_redirect_bpu_res}),
      .*
  );

  assign o_redirect_bpu_res_valid = 1'b1;

  genvar i;
  generate
    FrontendOP o_op_w[DECODE_WIDTH-1:0];
    logic o_op_valid_w[DECODE_WIDTH-1:0];
    DecodeOP dop[DECODE_WIDTH-1:0];

    for (i = 0; i < DECODE_WIDTH; i = i + 1) begin : riscv_decoder
      rv_decoder inst_riscv_decoder (
          .inst(i_ibuf_entry[i].inst),
          .dop (dop[i])
      );
      if (i == 0) begin : i_is_zero
        assign o_op_valid_w[i] = i_ibuf_entry_valid[i];
      end else begin : i_is_not_zero
        assign o_op_valid_w[i] = i_ibuf_entry_valid[i] & ~|redirect_valid[i-1:0];
      end
      assign o_op_w[i].dop = dop[i];
      assign o_op_w[i].pc = i_ibuf_entry[i].pc;
      assign o_op_w[i].inst = i_ibuf_entry[i].inst;

      assign i_data[i] = {o_op_valid_w[i], o_op_w[i]};
      assign {o_op_valid[i], o_op[i]} = o_data[i];

      // FrontendOP fop = o_op_w[i];
      // wire fop_valid = o_op_valid_w[i];

      BranchType branch_type;
      assign branch_type = dop[i].branch_type;
      wire is_jal = dop[i].fu_type[FU_BRU_BIT] && dop[i].fu_op == BRU_JAL;

      word_t pnpc, dnpc;
      assign pnpc = (i_ibuf_entry[i].bpu_res_valid & i_ibuf_entry[i].bpu_res.taken) ? i_ibuf_entry[i].bpu_res.target :  i_ibuf_entry[i].pc + 'h4;

      wire dnpc_valid = branch_type == BT_None | is_jal;
      assign dnpc = i_ibuf_entry[i].pc + (is_jal ? `IMM_WORD(dop[i].imm32) : 'h4);

      wire bpredict_miss = (pnpc != dnpc) & i_ibuf_entry_valid[i] & dnpc_valid;
      BPU_Res correct_res;
      always_comb begin
        correct_res = i_ibuf_entry[i].bpu_res;
        if (bpredict_miss) begin
          correct_res.taken = 1'b1;
          correct_res.branch_type = branch_type;
          correct_res.target = dnpc;
        end
      end
      assign o_op_w[i].bpu_res = correct_res;//bpredict_miss ? correct_res : i_ibuf_entry[i].bpu_res;
      assign o_op_w[i].bpu_res_valid = bpredict_miss | i_ibuf_entry[i].bpu_res_valid;

      assign redirect_valid[i] = bpredict_miss;
      assign redirect_pc[i] = o_op_w[i].pc;
      assign redirect_bpu_res[i] = correct_res;
    end

    assign sel_redirect_valid = |redirect_valid;
    MUX_PRIO #(DECODE_WIDTH, ($size(
        BPU_Res
    ))) mux_redirect_bpu_res (
        .sel (redirect_valid),
        .din (redirect_bpu_res),
        .dout(sel_redirect_bpu_res)
    );
    MUX_PRIO #(DECODE_WIDTH, `WORD_BITS) mux_redirect_pc (
        .sel (redirect_valid),
        .din (redirect_pc),
        .dout(sel_redirect_pc)
    );
    int decode_branch_total_inc;
    int decode_branch_btb_miss_inc;

    always_comb begin
      integer idx;
      decode_branch_total_inc = 'b0;
      decode_branch_btb_miss_inc = 'b0;
      for (idx = 0; idx < DECODE_WIDTH; idx = idx + 1) begin
        if (i_fire & o_op_valid_w[idx] & o_op_w[idx].dop.branch_type != BT_None)
          decode_branch_total_inc = decode_branch_total_inc + 'b1;
        if (i_fire & o_op_valid_w[idx] & (o_op_w[idx].dop.branch_type != BT_None) & ~i_ibuf_entry[idx].bpu_res_valid)
          decode_branch_btb_miss_inc = decode_branch_btb_miss_inc + 'b1;
      end
    end
    `PERF_EVENT_MULT(decode_branch_total, decode_branch_total_inc, 32);
    `PERF_EVENT_MULT(decode_branch_btb_miss, decode_branch_btb_miss_inc, 32);
    `PERF_EVENT(decode_redirect_num, o_redirect_valid);

  endgenerate

endmodule
